package it.eng.area118.mdocommon.android.command;

import it.eng.area118.mdocommon.Callback;
import it.eng.area118.mdocommon.Command;
//import it.eng.area118.mdocommon.android.log.BasicLogger;
import it.eng.area118.mdocommon.android.util.ConfigurationConstants;
import it.eng.area118.mdocommon.android.util.SharedPreferenceUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.TimeUnit;

import android.os.AsyncTask;
import android.os.Handler;

/**
 * 
 * @author Luigi Bifulco
 * 
 *         Android Realization for Command interface It uses the
 *         {@link AsyncTask} provided by Android SDK. The {@link Handler} is
 *         used to provide a thread queue dispatcher of job during the task
 *         execution. The execute in synch mode uses the get method on the
 *         AsyncTask making it a simple synch task.
 * 
 *         This is an abstract realization and needs to be extended: before(),
 *         after(), and doCommand() The only methods to be overridden in the
 *         concrete class.
 * 
 *         Developer attention is to be addressed to those methods only, let him
 *         focus with the concrete command purpose.
 * 
 *         That's All.
 * 
 * @param <Params>
 * @param <Result>
 */
public abstract class AndroidCommand<Params, Result> implements
		Command<Params, Result> {

	private static final String TAG = "AndroidCommand";

	protected Handler handler;

	protected Map<AsyncTask<Params, String, Result>, TaskEntry> map;

	protected AsyncTask<Params, String, Result> task;

	private int timeout = 10;

	private enum AndroidCommandState {
		STARTED, COMPLETED_SUCCESS, COMPLETED_FAILURE, CANCELLED;
	}

	private class TaskEntry {

		private Callback<Result> callback;

		private Timer timer;

		public TaskEntry(Callback<Result> callback, Timer timer) {
			this.callback = callback;
			this.timer = timer;
		}

		public Callback<Result> getCallback() {
			return callback;
		}

		public void setCallback(Callback<Result> callback) {
			this.callback = callback;
		}

		public Timer getTimer() {
			return timer;
		}

		public void setTimer(Timer timer) {
			this.timer = timer;
		}

	}

	private class TaskResult {

		private String result;

		private String reason;

		private Throwable throwable;

		public TaskResult(String result, String reason) {
			this.reason = reason;
			this.result = result;

		}

		public String getResult() {
			return result;
		}

		public void setResult(String result) {
			this.result = result;
		}

		public String getReason() {
			return reason;
		}

		public void setReason(String reason) {
			this.reason = reason;
		}

		public Throwable getThrowable() {
			return throwable;
		}

		public void setThrowable(Throwable throwable) {
			this.throwable = throwable;
		}

	}

	private class AndroidCommandAsyncTask extends
			AsyncTask<Params, String, Result> {

		private TaskResult taskResult = new TaskResult("", "");

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			TaskEntry entry = map.get(AndroidCommand.this.task);
			Callback<Result> callback = entry != null ? entry.getCallback()
					: null;
			if (callback != null) {
				callback.onBeforeExecute();
			}

		}

		protected void onPostExecute(final Result result) {
			// //BasicLogger.writeLog(TAG, "onPostExecute: " + result);
			handler.post(new Runnable() {

				@Override
				public void run() {
					after();
					TaskEntry entry = map.get(AndroidCommand.this.task);
					Callback<Result> callback = entry != null ? entry
							.getCallback() : null;
					String sResult = taskResult.getResult();
					if (sResult.equals(AndroidCommandState.COMPLETED_SUCCESS
							.name())) {
						if (callback != null) {
							callback.onSuccess(result);
							if (map.get(AndroidCommand.this.task).getTimer() != null) {
								map.get(AndroidCommand.this.task).getTimer()
										.cancel();
							}
						}
					} else if (sResult
							.equals(AndroidCommandState.COMPLETED_FAILURE
									.name())) {
						if (callback != null) {
							callback.onFailure(taskResult.getThrowable());
							if (map.get(AndroidCommand.this.task).getTimer() != null) {
								map.get(AndroidCommand.this.task).getTimer()
										.cancel();
							}
						}
					} else if (sResult.equals(AndroidCommandState.CANCELLED
							.name())) {
						if (callback != null) {
							callback.onCancelled();
							if (map.get(AndroidCommand.this.task).getTimer() != null) {
								map.get(AndroidCommand.this.task).getTimer()
										.cancel();
							}
						}
					}

				}
			});

		};

		@Override
		protected void onProgressUpdate(String... values) {
			// //BasicLogger.writeLog(TAG, "onProgressUpdate: " + values);
			String update = values[0];
			// BasicLogger.writeLog(TAG, "onProgressUpdate: " + update);
			if (update.equals(AndroidCommandState.STARTED.name())) {
				taskResult.setResult("");
			} else if (update.equals(AndroidCommandState.COMPLETED_SUCCESS
					.name())) {
				taskResult.setResult(AndroidCommandState.COMPLETED_SUCCESS
						.name());
			} else if (update.equals(AndroidCommandState.COMPLETED_FAILURE
					.name())) {
				taskResult.setResult(AndroidCommandState.COMPLETED_FAILURE
						.name());
			} else if (update.equals(AndroidCommandState.COMPLETED_FAILURE
					.name())) {
				taskResult.setResult(AndroidCommandState.CANCELLED.name());
			} else {

			}
		}

		@Override
		protected Result doInBackground(Params... params) {

			// Callback<Result> callback =
			// map.get(AndroidCommand.this.task).getCallback();
			// BasicLogger.writeLog(TAG, "doInBackground....");
			try {
				this.publishProgress(AndroidCommandState.STARTED.name());
				Result result = doCommand(params);
				this.publishProgress(AndroidCommandState.COMPLETED_SUCCESS
						.name());
				if (result == null) {
					// BasicLogger.writeLog(TAG,
					// "doInBackground result is null");
				}
				return result;
			} catch (Exception e) {
				e.printStackTrace();
				// BasicLogger.writeLog(TAG,
				// "doInBackground Exception...." + e.getMessage());
				this.publishProgress(AndroidCommandState.COMPLETED_FAILURE
						.name());
				taskResult.setReason(e.getMessage());
				taskResult.setThrowable(e);
			}
			return null;

		}

		protected void onCancelled() {
			// BasicLogger.writeLog(TAG, "onCancelled....");
			publishProgress(AndroidCommandState.CANCELLED.name());
		}
	}

	public AndroidCommand() {
		handler = new Handler();
		map = new HashMap<AsyncTask<Params, String, Result>, TaskEntry>();
		task = new AndroidCommandAsyncTask();
	}

	@Override
	public Result execute(Params... params) {
		try {

			AsyncTask<Params, String, Result> currentTask = task
					.execute(params);
			return currentTask.get(timeout, TimeUnit.SECONDS);
		} catch (Exception e) {
			// BasicLogger.writeLog(TAG, "execute Exception...." +
			// e.getMessage());
			e.printStackTrace();
			this.task.cancel(true);
		}
		return null;
	}

	@Override
	public void execute(final Callback<Result> callback, final Params... params) {
		try {

			// Timer timer = new Timer(true);
			map.put(AndroidCommand.this.task, new TaskEntry(callback, null));
			final AsyncTask<Params, String, Result> currentTask = task
					.execute(params);

		} catch (Exception e) {
			// BasicLogger.writeLog(TAG, "execute Exception...." +
			// e.getMessage());

			callback.onFailure(e);
			if (map.get(AndroidCommand.this.task).getTimer() != null) {
				map.get(AndroidCommand.this.task).getTimer().cancel();
			}

		}

	}

	/*
	 * utility method for flow control of an AndroidCommand these methods need
	 * to be extended for the application command
	 */
	/**
	 * All operations to be done before doCommand
	 */
	public abstract void before();

	/**
	 * All operations to be done after doCommand
	 */
	public abstract void after();

	/**
	 * The execution core operations to be done
	 */
	protected abstract Result doCommand(Params... params) throws Exception;

	public int getTimeout() {
		return timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

}
